/*
 * This file is part of connotea-java.
 *
 * connotea-java is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * connotea-java is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.connotea;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.join;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Static utility methods for validation and conversion of {@link String} data.
 * 
 * @author <a href="mailto:christopher.townson@googlemail.com">Christopher
 *         Townson</a>
 */
public class StringUtils {

    private static final DateFormat[] DATE_FORMATS = new DateFormat[] {
            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm':xxZ'"),
            new SimpleDateFormat("yyyy-MM-dd'T'HH':xx:xxZ'"),
            new SimpleDateFormat("yyyy-MM-dd'xx:xxZ'"),
            new SimpleDateFormat("yyyy-MM-'xxTxx:xx:xxZ'"),
            new SimpleDateFormat("yyyy'-xx-xxTxx:xx:xxZ'") };

    /**
     * Converts the provided {@link String} into an {@link Author}. The last
     * discrete word is assumed to be the author's last name and all prior words
     * are assumed to be the first name(s).
     * 
     * @param name the name
     * @return the {@link Author}
     */
    public static Author toAuthor(String name) {
        if (isBlank(name)) {
            return null;
        }
        String[] names = name.trim().split(" ");
        if (names.length < 1) {
            return null;
        }
        if (names.length == 1) {
            return new Author(null, names[0].trim());
        }
        if (names.length > 1) {
            int endIndex = names.length - 1;
            String first = join(names, " ", 0, endIndex);
            String last = names[endIndex];
            return new Author(first, last);
        }
        return null;
    }

    /**
     * Converts the provided {@link String} to a {@link BigDecimal}, catching
     * any exceptions thrown in the process and re-throwing as a
     * {@link ConnoteaRuntimeException}.
     * 
     * @param number the {@link String} to parse as a {@link BigDecimal}
     * @return the {@link BigDecimal}
     * @throws ConnoteaRuntimeException if the {@link String} could not be
     *             parsed as a {@link BigDecimal}
     * @see BigDecimal#BigDecimal(String)
     */
    public static BigDecimal toBigDecimal(String number)
            throws ConnoteaRuntimeException {
        try {
            return new BigDecimal(number);
        } catch (Exception e) {
            throw new ConnoteaRuntimeException(e);
        }
    }

    /**
     * Converts Connotea's string representations of booleans into Java
     * <code>boolean</code> primitives.
     * 
     * @param bool the boolean string to parse
     * @return <code>true</code> if the string is equal (ignoring case) to "1"
     *         or "on" or "yes", or returns <code>true</code>
     *         {@link Boolean#parseBoolean(String)}
     */
    public static boolean toBoolean(String bool) {
        return Boolean.getBoolean(bool) || "true".equalsIgnoreCase(bool)
                || "1".equals(bool) || "on".equalsIgnoreCase(bool)
                || "yes".equalsIgnoreCase(bool);
    }

    /**
     * Converts Connotea's string representations of dates into {@link Date}
     * instances.
     * 
     * @param date the date string to parse
     * @return the {@link Date}
     * @throws ConnoteaRuntimeException if the {@link String} could not be
     *             parsed as a {@link Date}
     */
    public static Date toDate(String date) throws ConnoteaRuntimeException {
        if (isBlank(date)) {
            return null;
        }
        for (int i = 0; i < DATE_FORMATS.length; i++) {
            try {
                return parse(date, i);
            } catch (Exception e) {
                if (i == (DATE_FORMATS.length - 1)) {
                    throw new ConnoteaRuntimeException(e);
                }
            }
        }
        return null;
    }

    /**
     * Converts {@link String} to an <code>int</code>, catching any exceptions
     * that are thrown in the process and re-throwing as a
     * {@link ConnoteaRuntimeException}.
     * 
     * @param number the number to parse
     * @return the <code>int</code>
     * @throws ConnoteaRuntimeException if the {@link String} could not be
     *             parsed as an <code>int</code>
     * @see Integer#parseInt(String)
     */
    public static int toInt(String number) throws ConnoteaRuntimeException {
        try {
            return Integer.parseInt(number);
        } catch (Throwable e) {
            throw new ConnoteaRuntimeException(e);
        }
    }

    /**
     * <p>
     * Converts {@link String} to a <code>long</code>, catching any exceptions
     * that are thrown in the process and re-throwing as a
     * {@link ConnoteaRuntimeException}.
     * </p>
     * 
     * @param number the number to parse
     * @return the <code>long</code>
     * @throws ConnoteaRuntimeException if the {@link String} could not be
     *             parsed as a <code>long</code>
     * @see Long#parseLong(String)
     */
    public static long toLong(String number) throws ConnoteaRuntimeException {
        try {
            return Long.parseLong(number);
        } catch (Throwable e) {
            throw new ConnoteaRuntimeException(e);
        }
    }

    /**
     * Does a bit of tidying-up and validation of relative, path-based URI
     * strings, removing double slashes etc.
     * 
     * @param path the path to normalise
     * @return the normalised path
     */
    public static String toNormalisedPath(String path) {
        // handle nulls by returning empty string
        if (isBlank(path)) {
            return "";
        }
        String myPath = new String(path);
        // first, trim
        myPath = myPath.trim();
        // replace backslashes with forward slashes
        if (myPath.indexOf("\\") >= 0) {
            myPath = myPath.replace("\\", "/");
        }
        // resolve occurrences of double forward slashes
        while (true) {
            int index = myPath.indexOf("//");
            if (index < 0) {
                break;
            }
            myPath = myPath.substring(0, index) + myPath.substring(index + 1);
        }
        // resolve occurrences of "/./" and "/../"
        while (true) {
            int index = myPath.indexOf("/./");
            if (index < 0) {
                break;
            }
            myPath = myPath.substring(0, index) + myPath.substring(index + 2);
        }
        while (true) {
            int index = myPath.indexOf("/../");
            if (index < 0) {
                break;
            }
            if (index == 0) {
                return (""); // Trying to go outside our context
            }
            int index2 = myPath.lastIndexOf('/', index - 1);
            myPath = myPath.substring(0, index2) + myPath.substring(index + 3);
        }
        // return the path
        return myPath;
    }

    private static Date parse(String date, int i) throws Exception {
        return DATE_FORMATS[i].parse(date);
    }

    private StringUtils() {
        // private constructor - class cannot be instantiated
    }
}
